<?php
namespace App\Lib;

use Symfony\Component\DomCrawler\Crawler;

class ParseXml
{

    const FIGURE_DIR = 'figures';
    private $crawler;

    private $html;
    private $count = 0;

    private $htmlTags = array(
        'p' => 'xml_p'
    );

    private $htmlLabel = array(
        '<p>' => '<xml_p>',
        '</p>' => '</xml_p>'
    );
    private $objects;



    public function __construct($filePath)
    {
        if (!file_exists($filePath)) {
            throw new \Exception("file can not found: " . $filePath, 1);
        }

        $this->crawler = new crawler();
        $contents = file_get_contents($filePath);
        // $contents = str_replace(array_keys($this->htmlLabel), array_values($this->htmlLabel), file_get_contents($filePath));
        // print_r($contents);exit;
        $this->crawler->addXmlContent($contents);

        $this->objects = $this->crawler->filterXPath('//sec[@sec-type="display-objects"]')->children();
    }


    public function getAuthors()
    {
        $crawler = $this->crawler->filterXPath('//article-meta/contrib-group/contrib[@contrib-type="author"]');
        if ($crawler->count() == 0) return array();

        return $crawler->each(function($item, $i) {
            $author = array();

            $surnameXpath = '//name/surname';
            $author['surname'] = $item->filterXPath('//name/surname')->count() ? trim($item->filterXPath('//name/surname')->text()) : '';
            $author['given-names'] = $item->filterXPath('//name/given-names')->count() ? trim($item->filterXPath('//name/given-names')->text()) : '';

            $author['name'] = $author['surname'] . ' ' . $author['given-names'];

            $aff = $item->filterXPath('//xref');
            if ($aff->count() > 0) {
                $author['affs'] = $aff->each(function($affItem, $i) {
                    return trim($affItem->text());
                });
            }
            return $author;
            
        });
    }

    public function getAffs()
    {
        $crawler = $this->crawler->filterXPath('//article-meta/aff');
        if ($crawler->count() == 0) return array();

        return $crawler->each(function($affItem, $i) {

            // return $this->getHtmlStringByNode($affItem, false);
            $aff = array();
            $label = $affItem->filter('label');

            if ($label->count() == 0) {
                $aff['label'] = 'no';
                $aff['info'] = $this->getHtmlStringByNode($affItem, false);
            } else {
                $aff['label'] = trim($label->text());
                $aff['info'] = $this->getHtmlStringByNode($affItem, false);
            }

            $emailCrawler = $affItem->filter('email');
            $aff['emails'] = $emailCrawler->each(function($item, $i) {
                return trim($item->text());
            });

            return $aff;
        });
    }

    public function getAuthorNotes()
    {
        $crawler = $this->crawler->filterXPath('//article-meta/author-notes');
        if ($crawler->count() == 0) return array();

        $children = $crawler->children();

        return $children->each(function($child, $i) {

            // return $this->getHtmlStringByNode($child, false);
            $note = array();
            $label = $child->filter('label');
            if ($label->count() == 0) {
                $note['label'] = 'no';
                $note['info'] = $this->getHtmlStringByNode($child, false);
            } else {
                $note['label'] = trim($label->text());
                $note['info'] = $this->getHtmlStringByNode($child, false);
            }

            // print_r($note);exit;
            return $note;
        });

    }

    public function getPublishDate($collection=false)
    {
        if ($collection) {
            $crawler = $this->crawler->filterXPath('//article-meta/pub-date[@pub-type="collection"]');

        } else {
            $crawler = $this->crawler->filterXPath('//article-meta/pub-date[@pub-type="epub"]');    
        }

        if ($crawler->count() == 0) return null;
        
        return $this->processDate($crawler);

    }

    public function getHistoryDate($type='received')
    {
        $crawler = $this->crawler->filterXPath('//article-meta/history/date[@date-type="' . $type . '"]');

        if ($crawler->count() == 0) return null;

        return $this->processDate($crawler);


    }

    private function processDate($crawler)
    {
        $day = $crawler->filter('day');
        $month = $crawler->filter('month');
        $year = $crawler->filter('year');
        if ($day->count() == 0 && $month->count() == 0 && $year->count() == 0) return null;

        if ($day->count() == 0) $day = '01';
        else $day = trim($day->text());

        
        if($month->count() == 0) $month = '01';
        else $month = trim($month->text());

        
        if ($year->count() == 0) $year = date('Y');
        else $year = trim($year->text());

        return $year . '-' . $month . '-' . $day;
    }

    public function getKeywords()
    {
        $crawler = $this->crawler->filterXPath('//article-meta/kwd-group/kwd');
        if ($crawler->count() == 0) return array();

        return $crawler->each(function($item, $i) {
            return $this->asText($item);
        });
    }

    public function getBody()
    {
        $crawler = $this->crawler->evaluate('//body');
        return $this->getHtmlStringByNode($crawler, false);
        
    }

    public function getBack()
    {
        $crawler = $this->crawler->evaluate('//back')->first();
        return $this->getHtmlStringByNode($crawler, false);
    }

    public function getSectionsByNode($node = null)
    {
        if (!$node) {
            $crawler = $this->crawler->evaluate('//body/sec');
        } else {
            $crawler = $node->filterXPath('//' . $node->nodeName() . '/sec');
        }
        if ($crawler->count() == 0) return array();

        $getSection = function ($secCrawler) {
            $sec['id'] = $secCrawler->extract('id')[0];
            $sec['sec-type'] = $secCrawler->extract('sec-type')[0];
            $title = $secCrawler->filterXPath('//sec/title');
            // print_r($title);exit;
            $sec['title'] = ($title->count() > 0) ? $title->html() : '';
            return $sec;
        };
        foreach ($crawler as $k => $sec) {
            $sCrawler = $crawler->eq($k);
            $sections[$k] = $getSection($sCrawler);
            $sections[$k]['children'] = $this->getSectionsByNode($sCrawler);
        }

        return $sections;

    }

    public function getHtmlStringByNode(Crawler $node, $withRootTag = true) //xbk
    {
        // $this->count++;
        if (
            $node->nodeName() == 'sec' && in_array($node->extract('sec-type')[0], array('display-objects'))
            || $node->nodeName() == 'object-id'
        ) { // figs && table
            return '';
        }

        if (in_array($node->nodeName(), array('ref', 'table', 'disp-formula'))) { // processing alone
            return $this->renderByNode($node, $withRootTag);
        }

        $children = $node->children();
        if ($children->count() == 0) {
            return $this->renderByNode($node, $withRootTag);
        }


        if (!$withRootTag) $xmlString = $node->html();
        else $xmlString = $this->renderByNode($node);
        foreach ($children as $k => $child) {
            $child = $children->eq($k);
            
            $html = $this->getHtmlStringByNode($child);
            
            $sameTagCount = $child->filterXPath('//' . $child->nodeName() . '/' . $child->nodeName())->count();
            $nodeName = $child->nodeName();

            if ($sameTagCount > 0) {
                $xmlString = preg_replace_callback('/<' . $nodeName . '[\s|>]([\s\S]*?<\/' . $nodeName . '>){' . ($sameTagCount + 1) . '}/', function($matches) use ($html) {
                    return $html;
                }, $xmlString, 1);
            } else {
                $xmlString = preg_replace('/<' . $child->nodeName() . '[\s|>][\s\S]*?<\/' . $child->nodeName() . '>/', $html, $xmlString, 1);
            }
            // if ($nodeName == 'fn-group') {
            //     print_r($html);
            //     echo '<hr>';
            //     echo '<hr>';
            //     print_r($xmlString);
            // }
            
        }


        $xmlString = $xmlString . $this->getObjectString($node);

        return $xmlString;
        
    }

    private function getObjectString($node) // parent node
    {
        
        $xrefObj = $node->filterXPath('//xref[@ref-type="fig" or @ref-type="table"]');

        $objStringArr = '';
        if ($xrefObj->count() > 0) {
            foreach ($xrefObj as $i => $xref) {
                $xref = $xrefObj->eq($i);
                foreach ($this->objects as $k => $object) {
                    $object = $this->objects->eq($k);
                    
                    if ($object->count() == 0) break;
                    if ($xref->extract('rid')[0] == $object->extract('id')[0]) {
                        $this->objects = $this->objects->reduce(function($node, $i) use ($xref) {
                            return $node->extract('id')[0] != $xref->extract('rid')[0];
                        });

                        $objStringArr .= $this->getHtmlStringByNode($object);

                    }
                }
            }
        }
        

        return $objStringArr;
    }


    public function renderByNode($node, $withRootTag = true)
    {
        $render = function($label, $content, $withTag=true, $attrs=array()) {
            $attrstring = '';
            foreach ($attrs as $key => $attr) {
                $attrstring .= $key . '="' . $attr . '" '; 
            }
            return $withTag ? '<' . $label . ' ' . $attrstring .'>' . $content . '</' . $label . '>' : $content;
        };

        $attrs = array();
        switch ($node->nodeName()) {
            case 'disp-formula':
                $html = $render('div', $this->getMathHtmlString($node), $withRootTag, array(
                    'class' => 'math-wrap',
                    'id' => $node->extract('id')[0]
                ));
                break;

            case 'email':
                $html = $render('a', $node->html(), $withRootTag, array('href' => 'mailto:' . $node->text()));
                break;

            case 'ref':
                $html = $render('li', $this->getRefHtmlString($node), $withRootTag, array('id' => $node->extract('id')[0]));
                break;

            case 'table-wrap':
                $html = $render('div', $this->getTableWrapHtmlString($node), $withRootTag, array(
                    'id' => 'body-' . $node->extract('id')[0],
                    'class' => 'table-wrap html-popup', 
                    'href' => "#" . $node->extract('id')[0]
                ));


                $html .= $render('div', $this->getTableWrapHtmlString($node, true), $withRootTag, array(
                    'class' => 'table-wrap-mfp mfp-hide', 
                    'id' => $node->extract('id')[0]
                ));
                break;

            case 'fig':
                $html = $render('div', $this->getFigHtmlString($node), $withRootTag, array(
                    'class' => 'fig-wrap html-popup', 
                    'id' => 'body-' . $node->extract('id')[0], 
                    'href' => "#" . $node->extract('id')[0]
                ));

                // magnificPopup
                $html .= $render('div', $this->getFigHtmlString($node, true), $withRootTag, array(
                    'class' => 'fig-wrap-mfp mfp-hide', 
                    'id' => $node->extract('id')[0]
                ));
                break;

            case 'graphic':
                $fileName = preg_replace('/\.\S+$/i', '.png', $node->extract('xlink:href')[0]);

                $html = '<img src="./' . self::FIGURE_DIR . '/' . $fileName . '"/>';
                break;

            case 'xref':
                $hrefId = $node->extract('rid')[0];
                $type = $node->extract('ref-type')[0];
                $attrs = array(
                    'href' => '#' . $hrefId,
                    'class' => 'a-xref-' . $type . (($type == 'fig') ? ' html-popup' : ''),
                    'data-ref-type' => $type,
                    'data-rid' => $hrefId
                );
                $html = $render('a', $node->html(), $withRootTag, $attrs);
                break;

            case 'label':
                if (in_array($node->parents()->first()->nodeName(), array('aff', 'fn', 'corresp'))) {
                    $html = '';
                } else {
                    $html = $render('b', $node->html(), $withRootTag);
                }
                break;

            case 'title':
                if ($node->parents()->eq(1)->nodeName() == 'sec') {
                    // echo 123;exit;
                    $attrs = array('class' => 'sub_title');
                }

                $html = $render('h3', $node->html(), $withRootTag, $attrs);
                break;
            case 'sec':
                $attrs = array(
                    'id' => $node->extract('id')[0],
                    'data-sec-type' => $node->extract('sec-type')[0],
                );

                $html = $render('section', $node->html(), $withRootTag, $attrs);
                break;
            default:
                $tags = array(
                    'p' => 'div',
                    'title' => 'h3',
                    // 'label' => 'sup',
                    'sec' => 'section',
                    'sc' => 'small',
                    'italic' => 'i',
                    'app-group' => 'section',
                    'notes' => 'section',
                    'ref-list' => 'ol',
                    'fn-group' => 'section',
                    'bold' => 'b',
                    'caption' => 'div',
                    'table' => 'table'
                );
                $nodeName = $label = $node->nodeName();

                if (array_key_exists($nodeName, $tags)) {
                    $label = $tags[$nodeName];
                } elseif (substr($nodeName, 0, 3) == 'mml') {
                    $label = substr($nodeName, 4);
                }

                $html = $render($label, $node->html(), $withRootTag);
                break;
        }

        return $html;
    }


    private function getMathHtmlString($node)
    {
        $html = $node->html();

        $math = $node->filterXPath('//disp-formula/mml:math');
        if ($math->count() > 0) {
            $mathString = '<div class="math-info"><math>' . str_replace('mml:', '', $math->html()) .'</math></div>';
            $html = preg_replace('/<mml:math[\s\S]*?\/mml:math>/i', $mathString , $html);
        }

        $label = $node->filterXPath('//disp-formula/label');
        if ($label->count() > 0) {
            $labelString = '<div class="math-label">' . $label->html() . '</div>';

            $html = preg_replace('/<label[\s\S]*?\/label>/i', $labelString, $html);
        }

        return $html;
    }

    private function getTableWrapHtmlString($table, $mfp=false)
    {
        $getEle = function($name) use ($table) {
            $node = $table->filterXPath('//table-wrap/' . $name);
            return $this->asText($node, $name == 'table' ? true : false); 
        };

        if ($mfp) {
            return '<div class="caption"><b>' . $getEle('label') . '</b>' . $getEle('caption') . '</div><div>' . $getEle('table') . '</div>';
        }

        return '<div class="caption"><b>' . $getEle('label') . '</b>' . $getEle('caption') . '</div><div class="table-icon" href="#"' . $table->extract('id')[0] . '><img alt="table" src="./icon/table.png"><a class="html-expand html-popup"></a></div>';


    }

    private function getFigHtmlString($fig, $mfp=false)
    {
        $getEle = function($name) use ($fig, $mfp) {
            switch ($name) {
                case 'graphics':
                    $crawler = $fig->filterXPath('//fig/graphic');
                    $imgs = $crawler->each(function($item, $i) {
                        return $this->asText($item);
                    });
                    if (!$mfp) {
                        $imgs[] = '<a class="html-expand html-popup" href="#' . $fig->extract('id')[0] . '"></a>';
                    }
                    return implode(' ', $imgs);

                    break;
                
                default:
                    $xPath = '//fig/' . $name;
                    break;
            }

            $node = $fig->filterXPath($xPath);
            return $this->asText($node, false);
        };

        // $graphic = $getEle('graphic');
        if ($mfp) {
            return '<div class="caption"><b>' . $getEle('label') . '</b>' . $getEle('caption') . '</div><div class="imgs">' . $getEle('graphics') . '</div>';
        }

        return '<div class="imgs">' . $getEle('graphics') . '</div><div class="caption"><b>' . $getEle('label') . '</b>' . $getEle('caption') . '</div>';
    }

    private function getRefHtmlString($ref)
    {
        $getEle = function($name) use ($ref) {
            switch ($name) {
                case 'label': 
                    $xPath = '//ref/label';
                    break;
                case 'type':
                    $citation = $ref->filterXPath('//element-citation');
                    return $citation->count() > 0 ? $citation->extract('publication-type')[0] : '';
                    break;
                case 'doi':
                    $xPath = '//element-citation/pub-id[@pub-id-type="doi"]';
                    break;

                case 'pmid':
                    $xPath = '//element-citation/pub-id[@pub-id-type="pmid"]';
                    break;

                case 'authors':
                    $xPath = '//element-citation/person-group/name';
                    $authors = $ref->filterXPath($xPath);
                    if ($authors->count() == 0) {
                        $authors = array();
                    } else {
                        $authors = $authors->each(function($author, $i) {
                            // print_r($author);exit;
                            return implode(', ', $author->children()->each(function($item, $i) {
                                return $item->html();
                            }));
                        });
                    }

                    return $authors;
                    break;

                default:
                    $xPath = '//element-citation/' . $name;
                    break;
                
            }


            $node = $ref->filterXPath($xPath);
            return $this->asText($node);
        };

        $attrs = array(
            'label', 
            'article-title', 
            'source', 
            'year', 
            'volume', 
            'fpage', 
            'lpage', 
            'doi', 
            'pmid', 
            'authors',
            'comment', 
            'publisher-name', 
            'publisher-loc',
            'type'
        );

        $ref = array();
        foreach ($attrs as $attr) {
            $ref[$attr] = $getEle($attr);
        }

        $authorString = implode('&', array_map(function($v){ return 'author=' . $v; }, $ref['authors']));
        $scholarLink = sprintf('http://scholar.google.com/scholar_lookup?title=%s&%s&publication_year=%u&journal=%s&volume=%u&pages=%s&doi=%s&pmid=%s', $ref['article-title'], $authorString, $ref['year'], $ref['source'], $ref['volume'], $ref['fpage'] . '–' . $ref['lpage'], $ref['doi'], $ref['pmid']);
        $ref['google_scholar'] = '<a href="' . $scholarLink . '"" class="google_scholar" target="_blank">Google Scholar</a>';
        
        $ref['crossref'] = '<a href="http://dx.doi.org/' . $ref['doi'] . '"" class="cross-ref">CrossRef</a>';
        $ref['pubmed'] = '<a href="http://www.ncbi.nlm.nih.gov/pubmed/' . $ref['pmid'] . '"" class="cross-ref">PubMed</a>';
        

        $getString = function($name) use ($ref) {
            $s = '';
            switch ($name) {
                case 'authors':
                    $s = implode('; ', $ref['authors']) . ' ';
                    break;
                case 'article-title':
                    $s = $ref['article-title'] . '. ';
                    break;
                case 'source':
                    $s = '<i>' . $ref['source'] . '</i> ';
                    break;
                case 'year':
                    $s = '<b>' . $ref['year'] . '</b>, ';
                    if ($ref['type'] == 'book') {
                        $s = $ref['year'] . '. ';
                    }
                    break;
                case 'volume':
                    $s = '<i>' . $ref['volume'] . '</i>, ';
                    break;

                case 'comment':
                    $s = $ref['comment'] . '; ';
                    break;
                case 'publisher-name':
                    $s = $ref['publisher-name'] . ': ';
                    break;
                case 'publisher-loc':
                    $s = $ref['publisher-loc'] . ', ';
                    break;
                case 'page':
                    if (empty($ref['fpage']) && empty($ref['lpage'])) {
                        return '';
                    } else {
                        return $ref['fpage'] . '–' . $ref['lpage'] . '. ';
                    }
                    break;
                case 'links':
                    if (!empty($ref['title'])) {
                        return '[' . $ref['google_scholar'] .']' . ($ref['doi'] ? ' [' . $ref['crossref'] . ']' : '') . ($ref['pmid'] ? ' [' . $ref['pubmed'] . ']' : '');
                    }
                    return '';
                    
                default:

                    break;
            }

            if (!empty($ref[$name])) {
                return $s;
            }

            return '';
        
        };
        
        if ($ref['type'] == 'book') {
            return implode('', array_map(function($v) use ($getString) {
                return $getString($v);
            }, array('authors', 'source', 'comment', 'publisher-name', 'publisher-loc', 'year')));
            
        }

        return implode('', array_map(function($v) use ($getString) {
            return $getString($v);
        }, array('authors', 'article-title', 'source', 'year', 'volume', 'page', 'links')));
        
    }


    public function __get($name)
    {
        if (isset($this->$name)) {
            return $this->$name;
        }

        switch ($name) {
            case 'journal_name':
                $xpath = '//journal-meta/journal-title-group/journal-title';
                break;

            case 'issn':
                $xpath = '//journal-meta/issn[@pub-type="epub"]';
                break;

            case 'publisher':
                $xpath = '//journal-meta/publisher/publisher-name';
                break;

            case 'doi':
                $xpath = '//article-meta/article-id[@pub-id-type="doi"]';
                break;

            case 'article_type':
                $xpath = '//article-meta/article-categories/subj-group/subject';
                break;

            case 'article_title':
                $xpath = '//article-meta/title-group/article-title';
                break;

            case 'volume':
                $xpath = '//article-meta/volume';
                break;

            case 'issue':
                $xpath = '//article-meta/issue';
                break;

            case 'elocation_id':
                $xpath = '//article-meta/elocation-id';
                break;

            case 'received_date':
                return $this->getHistoryDate('received');
                break;

            case 'accepted_date':
                return $this->getHistoryDate('accepted');
                break;

            case 'publish_date':
                return $this->getPublishDate();
                break;
            
            case 'publish_year':
                return date('Y', strtotime($this->getPublishDate()));
                break;
            
            case 'permissions':
                $xpath = '//article-meta/permissions';
                break;

            case 'abstract':
                $xpath = '//article-meta/abstract';
                break;

            case 'keywords': // return array
                return $this->getKeywords();
                break;
            default:
                print_r($name);exit;
                break;
        }

        $crawler = $this->crawler->filterXPath($xpath);
        return $this->asText($crawler);
    }

    public function asText($crawler, $withRootTag=false)
    {
        if ($crawler->count() == 0) return '';
        return $this->getHtmlStringByNode($crawler, $withRootTag);
        // return trim(str_replace(
        //     array('<italic>', '</italic>', '<bold>', '</bold>', '<sc>', '</sc>', '<sans_serif>', '</sans_serif>', '<monospace>', '</monospace>', '<underline>', '</underline>', '<overline>', '</overline>'), 
        //     array('<i>', '</i>', '<b>', '</b>', '<span style="font-variant: small-caps">', '</span>', '<span style="font-family: Sans-Serif">', '</span>', '<tt>', '</tt>', '<u>', '</u>', '<span style="text-decoration: overline">', '</span>'),
        //     $crawler->html()
        // ));
    }
}